Option Explicit On

Imports VB = Microsoft.VisualBasic

Public Module modFileDIR
    Private Declare Function GetShortPathName Lib "kernel32" Alias "GetShortPathNameA" (ByVal lpszLongPath As String, ByVal lpszShortPath As String, ByVal lBuffer As Int32) As Int32
    Private Declare Function GetFileAttributes Lib "kernel32" Alias "GetFileAttributesA" (ByVal lpFileName As String) As Int32

    Private Const MAX_PATH = 260
    Private Const FILE_ATTRIBUTE_DIRECTORY = &H10
    Private Const FILE_ATTRIBUTE_SYSTEM = &H4
    Private Const FILE_ATTRIBUTE_HIDDEN = &H2
    Private Const FILE_ATTRIBUTE_READONLY = &H1
    Private Const ERROR_NO_MORE_FILES = 18&

#Region "Classes"
    Public Class clsINISection
        Private cstrSectionName As String
        Private clstINISettings As New List(Of clsINISetting)

        Public Sub New(ByVal pstrSectionName As String)
            cstrSectionName = pstrSectionName
        End Sub

        Public ReadOnly Property SectionName As String
            Get
                Return cstrSectionName
            End Get
        End Property

        Public ReadOnly Property Settings As List(Of clsINISetting)
            Get
                Return clstINISettings
            End Get
        End Property

        Friend Sub AddSetting(ByVal pNewSetting As clsINISetting)
            clstINISettings.Add(pNewSetting)
        End Sub
    End Class

    Public Class clsINISetting
        Private cstrSettingName As String
        Private cstrSettingValue As String

        Public Sub New(ByVal pstrSettingName As String, ByVal pstrSettingValue As String)
            cstrSettingName = pstrSettingName
            cstrSettingValue = pstrSettingValue
        End Sub

        Public ReadOnly Property SettingName As String
            Get
                Return cstrSettingName
            End Get
        End Property

        Public ReadOnly Property SettingValue As String
            Get
                Return cstrSettingValue
            End Get
        End Property
    End Class
#End Region

#Region "Structures"
    Private Structure FILETIME
        Dim dwLowDateTime As Long
        Dim dwHighDateTime As Long
    End Structure

    Private Structure WIN32_FIND_DATA
        Dim dwFileAttributes As Long
        Dim ftCreationTime As FILETIME
        Dim ftLastAccessTime As FILETIME
        Dim ftLastWriteTime As FILETIME
        Dim nFileSizeHigh As Long
        Dim nFileSizeLow As Long
        Dim dwReserved0 As Long
        Dim dwReserved1 As Long
        Shared cFileName As New String("", MAX_PATH)
        Shared cAlternate As New String("", 14)
    End Structure

    Public Structure FileType
        Dim Path As String
        Dim Name As String
        Dim FileAttribute As FILE_ATTRIBUTE
    End Structure
#End Region

#Region "Enums"
    Public Enum FILE_ATTRIBUTE
        ATTRIBUTE_Alias = &H40
        ATTRIBUTE_Archive = &H20
        ATTRIBUTE_Directory = &H10
        ATTRIBUTE_Hidden = &H2
        ATTRIBUTE_Normal = &H80
        ATTRIBUTE_ReadOnly = &H1
        ATTRIBUTE_System = &H4
        ATTRIBUTE_Temporary = &H100
    End Enum
#End Region

#Region "File/Dir"
    ''' <summary>
    ''' Determines whether the given directory 'pstrPath' exists or not
    ''' </summary>
    ''' <param name="pstrPath"></param>
    ''' <returns>Returns true if the directory exists</returns>
    ''' <remarks></remarks>
    Public Function DirectoryExists(ByRef pstrPath As String) As Boolean
        Dim strShortPath As String
        Dim intFileAttributes As FILE_ATTRIBUTE

        DirectoryExists = False
        strShortPath = CreateShortPath(pstrPath)

        If strShortPath <> "" Then
            'If it returns an empty string, then file doesn't exist.  If it does, then we need to test to see if it is a directory
            intFileAttributes = GetFileAttributes(pstrPath)
            'returns -1 if invalid handle
            If intFileAttributes <> -1 Then
                If intFileAttributes And FILE_ATTRIBUTE.ATTRIBUTE_Directory Then
                    DirectoryExists = True
                End If
            End If
        End If
    End Function

    Private Function CreateShortPath(ByRef pstrFullPath As String) As String
        Dim intBufferLength As Int32
        Dim strBuffer As New String("", MAX_PATH)

        intBufferLength = GetShortPathName(pstrFullPath, strBuffer, MAX_PATH)

        If intBufferLength < 1 Then
            'file doesn't exist
            CreateShortPath = ""
        Else
            CreateShortPath = Mid$(strBuffer, 1, intBufferLength)
        End If
    End Function

    ''' <summary>
    ''' Reads in an entire file's contents
    ''' </summary>
    ''' <param name="pstrFilename">The file path</param>
    ''' <returns>A string containing the entire file's text contents</returns>
    ''' <remarks></remarks>
    Public Function GetAllBinaryData(ByVal pstrFilename As String) As ClsError
        Try
            Dim Stream As New System.IO.StreamReader(pstrFilename)

            GetAllBinaryData = New ClsError(False, Nothing, Stream.ReadToEnd)
            Stream.Close()
            Stream = Nothing
        Catch ex As Exception
            GetAllBinaryData = New ClsError(True, ex, "")
        End Try
    End Function

    ''' <summary>
    ''' Reads in an entire file's binary contents
    ''' </summary>
    ''' <param name="pstrFilename">The file path</param>
    ''' <returns>A string containing the entire file's binary contents</returns>
    ''' <remarks></remarks>
    Public Function GetAllFileData(ByVal pstrFilename As String, Optional pbolAsByteArray As Boolean = True) As ClsError
        Try
            If pbolAsByteArray Then
                GetAllFileData = New ClsError(False, Nothing, My.Computer.FileSystem.ReadAllBytes(pstrFilename))
            Else
                GetAllFileData = New ClsError(False, Nothing, My.Computer.FileSystem.ReadAllText(pstrFilename))
            End If
        Catch ex As Exception
            GetAllFileData = New ClsError(True, ex, "")
        End Try
    End Function

    ''' <summary>
    ''' Strips the file extension (.XYZ) off of the filepath
    ''' </summary>
    ''' <param name="pstrPath">The path to the file</param>
    ''' <returns>Returns the path to the file without the "." or the file extension</returns>
    ''' <remarks></remarks>
    Public Function StripExtension(ByVal pstrPath As String) As String
        StripExtension = pstrPath   'If no "." is found, then do not modify the text passed in

        For i As Integer = Len(pstrPath) To 1 Step -1   'Search for the last "." starting from the end of the path
            If Mid(pstrPath, i, 1) = "." Then
                StripExtension = Left(pstrPath, i - 1)  'Strip the file extension
                Exit Function
            End If
        Next i
    End Function

    ''' <summary>
    ''' Extracts the directory containing the file pstrFilePath
    ''' </summary>
    ''' <param name="pstrFilePath">The path to the file</param>
    ''' <returns>Returns the directory containing the file</returns>
    ''' <remarks>This routine extracts the directory location immediately above (containing) the file</remarks>
    Public Function GetContainingDirectory(ByVal pstrFilePath As String) As String
        Dim intIndex As Integer = 0 'Tracks the index of the "\"s within the path
        Dim intLastIndex As Integer = -1    'Tracks the last good location of a "\"

        Try
            'Loop through the string and find the last "\"
            Do
                intIndex = InStr(intIndex + 1, pstrFilePath, "\")
                If intIndex > 0 Then intLastIndex = intIndex 'Record the last found "\"
            Loop While intIndex > 0

            If intLastIndex <> -1 And intLastIndex > 0 Then 'If a "\" was found
                Return Left(pstrFilePath, intLastIndex) 'Extract the containing directory
            Else
                Return ""   'Error; return nothing
            End If
        Catch ex As Exception
            Return ""       'Error; return nothing
        End Try
    End Function
#End Region

#Region "Ini File"
    '************************************************************
    'Reads in all the initialization parameters from the ini file
    '************************************************************
    Public Function ReadINI(ByVal pstrFileName As String) As ClsError
        Dim strFileData As String
        Dim astrINISections() As String
        Dim astrINISectionLines() As String
        Dim astrINISectionLineData() As String
        Dim lstINISections As New List(Of clsINISection)
        Dim errResult As ClsError

        errResult = GetAllFileData(pstrFileName, False) 'Read in all the data from the initialization file

        If errResult.HasError = False Then
            strFileData = errResult.ReturnData
            astrINISections = Split(strFileData, "[")   'Split the INI file into its constituent sections.  each section starts with [

            If UBound(astrINISections) > 0 Then 'Make sure at least one section was found
                For i As Integer = 1 To UBound(astrINISections) 'element 0 should be blank, because [ is the first character in the ini file
                    astrINISectionLines = Split(astrINISections(i), vbCrLf) 'Split the section into individual lines (including header)
                    If VB.Right(astrINISectionLines(0), 1) = "]" Then   'Make sure the section header is properly terminated
                        'Extract the section header name from between [ and ]
                        lstINISections.Add(New clsINISection(VB.Left(astrINISectionLines(0), Len(astrINISectionLines(0)) - 1)))
                        For j As Integer = 1 To UBound(astrINISectionLines) 'Process the rest of the lines in this section
                            If astrINISectionLines(j) <> "" Then
                                astrINISectionLineData = Split(astrINISectionLines(j), "=")
                                If UBound(astrINISectionLineData) = 1 Then  'There should be a parameter name and a value
                                    lstINISections.Item(lstINISections.Count - 1).AddSetting(New clsINISetting(astrINISectionLineData(0), astrINISectionLineData(1)))
                                Else    'Error, INI setting does not contain 1 equal sign
                                    ReadINI = New ClsError(True, Nothing, "INI line does not contain '='")
                                End If
                            End If 'Emtpy INI file line
                        Next j
                    Else    'Section header missing ending ]
                        ReadINI = New ClsError(True, Nothing, "INI File corrupt.  Section header missing ]")
                    End If
                Next i

                ReadINI = New ClsError(False, Nothing, lstINISections)
            Else    'No section header exists in the INI file
                ReadINI = New ClsError(True, Nothing, "INI File corrupt.  No sections found.")
            End If
        Else    'Failed to read file
            ReadINI = New ClsError(True, errResult.Exception, "Unable to read INI file.")
        End If
    End Function

    ''' <summary>
    ''' Extracts a specific Section->Setting's value from the INI file's data
    ''' </summary>
    ''' <param name="pclsINISections">the structure containing all the INI file's data</param>
    ''' <param name="pstrSectionName">The section name</param>
    ''' <param name="pstrSettingName">The setting name to retrieve</param>
    ''' <returns>Returns the value of a setting in the INI file</returns>
    ''' <remarks></remarks>
    Public Function GetINIData(ByVal pclsINISections As List(Of clsINISection), ByVal pstrSectionName As String, ByVal pstrSettingName As String) As ClsError
        If pclsINISections IsNot Nothing Then   'Make sure the INI file had some data
            For i As Integer = 0 To pclsINISections.Count - 1   'Check each section
                If pclsINISections(i).SectionName = pstrSectionName Then    'Find the right section
                    For j As Integer = 0 To pclsINISections(i).Settings.Count - 1   'Check each setting
                        If pclsINISections(i).Settings(j).SettingName = pstrSettingName Then    'Find the right setting
                            GetINIData = New ClsError(False, Nothing, pclsINISections(i).Settings(j).SettingValue)  'Return the value
                            Exit Function
                        End If
                    Next j
                End If
            Next i

            GetINIData = New ClsError(True, Nothing, "Section '" & pstrSectionName & "', Setting '" & pstrSettingName & "' Not Found.")
        Else
            GetINIData = New ClsError(True, Nothing, "No INI file data exists")
        End If
    End Function
#End Region
End Module
